﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using McTools.Xrm.Connection;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Metadata;
using RibbonBrowser.Helpers;
using Tanguy.WinForm.Utilities.DelegatesHelpers;

namespace RibbonBrowser
{
    public partial class MainForm : Form
    {
        #region Variables

        ConnectionManager cManager;

        CrmConnectionStatusBar ccsb;

        IOrganizationService service;

        /// <summary>
        /// List of entities
        /// </summary>
        List<EntityMetadata> entitiesCache;

        string currentSelectedEntity;

        #endregion

        #region Constructor

        public MainForm()
        {
            InitializeComponent();

            this.Text += " (v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + ")";

            this.cManager = new ConnectionManager(this);
            this.cManager.ConnectionSucceed += new ConnectionManager.ConnectionSucceedEventHandler(cManager_ConnectionSucceed);
            this.cManager.ConnectionFailed += new ConnectionManager.ConnectionFailedEventHandler(cManager_ConnectionFailed);
            this.cManager.StepChanged += new ConnectionManager.StepChangedEventHandler(cManager_StepChanged);

            ccsb = new CrmConnectionStatusBar(this.cManager);
            this.Controls.Add(ccsb);
        }

        #endregion

        #region Connections Methods

        void cManager_StepChanged(object sender, StepChangedEventArgs e)
        {
            this.ccsb.SetMessage(e.CurrentStep);
        }

        void cManager_ConnectionFailed(object sender, ConnectionFailedEventArgs e)
        {
            this.ccsb.SetMessage(e.FailureReason);
            MessageBox.Show(e.FailureReason, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        void cManager_ConnectionSucceed(object sender, ConnectionSucceedEventArgs e)
        {
            this.service = e.OrganizationService;

            if (e.Parameter == null)
            {
                this.ccsb.SetConnectionStatus(true, e.ConnectionDetail);
                CommonDelegates.SetCursor(this, Cursors.WaitCursor);
                this.ccsb.SetMessage("Loading entities...");

                // Caching entities
                this.entitiesCache = MetadataHelper.RetrieveEntities(this.service);

                this.ccsb.SetMessage("Displaying entities...");

                // Filling entities list
                this.FillEntitiesList();

                this.ccsb.SetMessage(string.Empty);
                CommonDelegates.SetCursor(this, Cursors.Default);
            }
            else
            {
                this.DisplayApplicationRibbon();
            }
        }

        #endregion

        #region Main ToolStrip Handlers

        #region Fill Entities

        private void tsbLoadEntities_Click(object sender, EventArgs e)
        {
            if (this.service == null)
            {
                this.cManager.AskForConnection(null);
            }
            else
            {
                this.lvEntities.Items.Clear();
                this.gbEntities.Enabled = false;

                BackgroundWorker bwFillEntities = new BackgroundWorker();
                bwFillEntities.DoWork += new DoWorkEventHandler(bwFillEntities_DoWork);
                bwFillEntities.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwFillEntities_RunWorkerCompleted);
                bwFillEntities.RunWorkerAsync();
            }
        }

        void bwFillEntities_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                string errorMessage = CrmExceptionHelper.GetErrorMessage(e.Error, true);
                CommonDelegates.DisplayMessageBox(this, errorMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        void bwFillEntities_DoWork(object sender, DoWorkEventArgs e)
        {
            CommonDelegates.SetCursor(this, Cursors.WaitCursor);
            this.ccsb.SetMessage("Loading entities ...");

            // Caching entities
            this.entitiesCache = MetadataHelper.RetrieveEntities(this.service);

            this.ccsb.SetMessage("Displaying entities...");

            // Filling entities list
            this.FillEntitiesList();

            this.ccsb.SetMessage(string.Empty);
            CommonDelegates.SetCursor(this, Cursors.Default);
        }

        /// <summary>
        /// Fills the entities listview
        /// </summary>
        public void FillEntitiesList()
        {
            try
            {
                ListViewDelegates.ClearItems(this.lvEntities);

                foreach (EntityMetadata emd in entitiesCache)
                {
                    ListViewItem item = new ListViewItem();
                    item.Text = emd.DisplayName.UserLocalizedLabel.Label;
                    item.Tag = emd.LogicalName;
                    ListViewDelegates.AddItem(this.lvEntities, item);
                }

                ColumnHeader header = new ColumnHeader()
                {
                    Text = "Display name",
                    Width = 160
                };

                ListViewDelegates.AddColumn(this.lvEntities, header);
                GroupBoxDelegates.SetEnableState(this.gbEntities, true);
            }
            catch (Exception error)
            {
                string errorMessage = CrmExceptionHelper.GetErrorMessage(error, true);
                CommonDelegates.DisplayMessageBox(this, errorMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion

        #region Save Ribbon

        private void tsbSaveRibbon_Click(object sender, EventArgs e)
        {
            if (this.tvRibbon.Nodes.Count == 0)
            {
                MessageBox.Show(this, "There is no ribbon to save...", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else
            {
                this.tsbSaveRibbon.Enabled = false;
                this.tsbLoadEntities.Enabled = false;

                SaveFileDialog sfd = new SaveFileDialog()
                {
                    Filter = "XML file (*.xml)|*.xml",
                    Title = "Ribbon Xml file location",
                    FileName = "ribbon.xml"
                };

                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    CommonDelegates.SetCursor(this, Cursors.WaitCursor);
                    this.ccsb.SetMessage("Saving Ribbon definition to Xml file...");

                    using (StreamWriter sw = new StreamWriter(sfd.FileName, true))
                    {
                        sw.WriteLine(this.IndentXMLString(((XmlNode)this.tvRibbon.TopNode.Tag).OuterXml));
                    }

                    this.ccsb.SetMessage("Done!");
                    CommonDelegates.SetCursor(this, Cursors.Default);
                    MessageBox.Show(this, "Ribbon saved to " + sfd.FileName, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        private string IndentXMLString(string xml)
        {
            MemoryStream ms = new MemoryStream();
            XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.Unicode);
            XmlDocument doc = new XmlDocument();

            try
            {
                doc.LoadXml(xml);

                xtw.Formatting = Formatting.Indented;
                doc.WriteContentTo(xtw);
                xtw.Flush();
                ms.Seek(0, SeekOrigin.Begin);
                StreamReader sr = new StreamReader(ms);
                return sr.ReadToEnd();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                return null;
            }
        }

        #endregion

        #region Display About Form

        private void tsbAbout_Click(object sender, EventArgs e)
        {
            AboutForm aForm = new AboutForm();
            aForm.StartPosition = FormStartPosition.CenterParent;
            aForm.ShowDialog();
        }

        #endregion

        #endregion

        #region ListViews Handlers

        #region Fill Ribbon

        private void lvEntities_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.lvEntities.SelectedItems.Count > 0)
            {
                // Reinit other controls
                this.tvRibbon.Nodes.Clear();

                this.Cursor = Cursors.WaitCursor;
                this.ccsb.SetMessage("Retrieving ribbon definition...");

                // Launch treatment
                BackgroundWorker bwRetrieveRibbon = new BackgroundWorker();
                bwRetrieveRibbon.DoWork += new DoWorkEventHandler(bwRetrieveRibbon_DoWork);
                bwRetrieveRibbon.RunWorkerAsync(this.lvEntities.SelectedItems[0].Tag.ToString());
                bwRetrieveRibbon.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwRetrieveRibbon_RunWorkerCompleted);
            }
        }

        void bwRetrieveRibbon_DoWork(object sender, DoWorkEventArgs e)
        {
            string entityDisplayName = e.Argument.ToString();

            RetrieveEntityRibbonRequest entRibReq = new RetrieveEntityRibbonRequest()
            {
                RibbonLocationFilter = RibbonLocationFilters.All,
                EntityName = entityDisplayName
            };

            RetrieveEntityRibbonResponse entRibResp = (RetrieveEntityRibbonResponse)this.service.Execute(entRibReq);

            string result = this.unzipRibbon(entRibResp.CompressedEntityXml);
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(result);

            MethodInvoker buildTreeView = delegate
            {
                TreeNode rootNode = new TreeNode("RibbonDefinitions");
                rootNode.Tag = doc.DocumentElement;
                this.tvRibbon.Nodes.Add(rootNode);

                this.AddNodeToTreeView(this.tvRibbon.TopNode, doc.SelectSingleNode("RibbonDefinitions/RibbonDefinition"));

                this.tvRibbon.TopNode.Expand();
            };

            if (this.tvRibbon.InvokeRequired)
            {
                this.tvRibbon.Invoke(buildTreeView);
            }
            else
            {
                buildTreeView();
            }

            currentSelectedEntity = entityDisplayName;
        }

        void bwRetrieveRibbon_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error == null && !e.Cancelled)
            {
                this.Cursor = Cursors.Default;
                this.ccsb.SetMessage(string.Empty);
            }
        }

        private void tsbDisplayApplicationRibbon_Click(object sender, EventArgs e)
        {
            if (this.service == null)
            {
                this.cManager.AskForConnection("application");
            }
            else
            {
                this.DisplayApplicationRibbon();
            }
        }

        void DisplayApplicationRibbon()
        {
            // Reinit other controls
            this.tvRibbon.Nodes.Clear();

            CommonDelegates.SetCursor(this, Cursors.WaitCursor);
            this.ccsb.SetMessage("Retrieving ribbon definition...");

            // Launch treatment
            BackgroundWorker bwRetrieveApplicationRibbon = new BackgroundWorker();
            bwRetrieveApplicationRibbon.DoWork += new DoWorkEventHandler(bwRetrieveApplicationRibbon_DoWork);
            bwRetrieveApplicationRibbon.RunWorkerAsync();
            bwRetrieveApplicationRibbon.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwRetrieveApplicationRibbon_RunWorkerCompleted);
        }

        void bwRetrieveApplicationRibbon_DoWork(object sender, DoWorkEventArgs e)
        {
            RetrieveApplicationRibbonRequest request = new RetrieveApplicationRibbonRequest();
            RetrieveApplicationRibbonResponse response = (RetrieveApplicationRibbonResponse)this.service.Execute(request);

            string result = this.unzipRibbon(response.CompressedApplicationRibbonXml);
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(result);

            MethodInvoker buildTreeView = delegate
            {
                TreeNode rootNode = new TreeNode("RibbonDefinitions");
                rootNode.Tag = doc.DocumentElement;
                this.tvRibbon.Nodes.Add(rootNode);

                this.AddNodeToTreeView(this.tvRibbon.TopNode, doc.SelectSingleNode("RibbonDefinitions/RibbonDefinition"));

                this.tvRibbon.TopNode.Expand();
            };

            if (this.tvRibbon.InvokeRequired)
            {
                this.tvRibbon.Invoke(buildTreeView);
            }
            else
            {
                buildTreeView();
            }
        }

        void bwRetrieveApplicationRibbon_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error == null && !e.Cancelled)
            {
                CommonDelegates.SetCursor(this, Cursors.Default);
                this.ccsb.SetMessage(string.Empty);
                this.tsbSaveRibbon.Enabled = true;
            }
        }



        #endregion

        #endregion

        /// <summary>
        /// A helper method that decompresses the the Ribbon data returned
        /// </summary>
        /// <param name="data">The compressed ribbon data</param>
        /// <returns></returns>
        public string unzipRibbon(byte[] data)
        {
            System.IO.Packaging.ZipPackage package = null;
            MemoryStream memStream = null;

            memStream = new MemoryStream();
            memStream.Write(data, 0, data.Length);
            package = (ZipPackage)ZipPackage.Open(memStream, FileMode.Open);

            ZipPackagePart part = (ZipPackagePart)package.GetPart(new Uri("/RibbonXml.xml", UriKind.Relative));
            StreamReader reader = new StreamReader(part.GetStream());
            string text = reader.ReadToEnd();

            return text;
        }

        private void AddNodeToTreeView(TreeNode parentNode, XmlNode xmlNode)
        {
            TreeNode node = new TreeNode(xmlNode.Name);
            node.Tag = xmlNode;

            if (xmlNode.NodeType != XmlNodeType.Comment)
            {
                if (xmlNode.Attributes["Id"] != null)
                {
                    node.Text += " (" + xmlNode.Attributes["Id"].Value + ")";
                    node.Name = xmlNode.Attributes["Id"].Value;
                }

                if (node.Text == "OverflowSection" && xmlNode.Attributes["TemplateAlias"] != null)
                {
                    node.Text += " (" + xmlNode.Attributes["TemplateAlias"].Value + ")";
                    node.Name = xmlNode.Attributes["TemplateAlias"].Value;
                }

                if (node.Text == "Section" && xmlNode.Attributes["Type"] != null)
                {
                    node.Text += " (" + xmlNode.Attributes["Type"].Value + ")";
                }

                if (node.Text == "Layout" && xmlNode.Attributes["Title"] != null)
                {
                    node.Text += " (" + xmlNode.Attributes["Title"].Value + ")";
                    node.Name = xmlNode.Attributes["Title"].Value;
                }
            }

            parentNode.Nodes.Add(node);

            foreach (XmlNode childXmlNode in xmlNode.ChildNodes)
            {
                if (childXmlNode.NodeType != XmlNodeType.Comment)
                {
                    this.AddNodeToTreeView(node, childXmlNode);
                }
            }

            if (node.Level < 2)
            {
                node.Expand();
            }
        }

        private void tvRibbon_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            this.panelAttributes.Controls.Clear();

            TreeNode currentNode = e.Node;
            XmlNode currentXmlNode = (XmlNode)currentNode.Tag;

            int index = 0;

            this.txtXml.Text = this.IndentXMLString(currentXmlNode.OuterXml);

            foreach (XmlAttribute attribute in currentXmlNode.Attributes)
            {
                System.Windows.Forms.Label lbl = new System.Windows.Forms.Label()
                {
                    Name = "lbl" + attribute.Name,
                    Text = attribute.Name,
                    Width = 100,
                    Location = new Point(0, index)
                };

                if (attribute.Name == "Id")
                {
                    lbl.Font = new Font(FontFamily.GenericSansSerif, (float)8.25, FontStyle.Bold);
                    lbl.ForeColor = Color.Red;
                }
                this.panelAttributes.Controls.Add(lbl);


                if (attribute.Name.ToLower() == "template" ||
                    attribute.Name.ToLower().IndexOf("command") >= 0 ||
                    attribute.Name.ToLower().IndexOf("id") >= 0 && e.Node.Parent != null 
                    && e.Node.Parent.Parent != null && e.Node.Parent.Parent.Text.StartsWith("CommandDefinition"))
                {
                    TextBox txtBox = new TextBox()
                    {
                        Name = "txt" + attribute.Name,
                        Text = attribute.Value,
                        Width = 250,
                        Location = new Point(110, index),
                        ReadOnly = true,
                        Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top
                    };

                    Button btn = new Button()
                    {
                        Name = "btn" + attribute.Name,
                        Text = "Go",
                        Width= 40,
                        Location = new Point(370, index - 2),
                        Anchor = AnchorStyles.Right | AnchorStyles.Top,
                        Tag = attribute.Value
                    };

                    btn.Click += new EventHandler(btn_Click);

                    this.panelAttributes.Controls.Add(txtBox);
                    this.panelAttributes.Controls.Add(btn);
                }
                else if (attribute.Name.ToLower() == "templatealias" && !e.Node.Text.ToLower().StartsWith("overflowsection") && !e.Node.Text.ToLower().StartsWith("controlref"))
                {
                    TextBox txtBox = new TextBox()
                    {
                        Name = "txt" + attribute.Name,
                        Text = attribute.Value,
                        Width = 250,
                        Location = new Point(110, index),
                        ReadOnly = true,
                        Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top
                    };

                    TreeNode node = e.Node;

                    while (node != null && !node.Text.StartsWith("Group"))
                    {
                        node = node.Parent;
                    }

                    XmlNode maxSizeNode = ((XmlNode)node.Tag).ParentNode.ParentNode.SelectSingleNode("Scaling/MaxSize[@GroupId='" + ((XmlNode)node.Tag).Attributes["Id"].Value + "']");

                    Button btn = new Button()
                    {
                        Name = "btn" + attribute.Name,
                        Text = "Go",
                        Width = 40,
                        Location = new Point(370, index - 2),
                        Anchor = AnchorStyles.Right | AnchorStyles.Top,
                        Tag = ((XmlNode)node.Tag).Attributes["Template"].Value + ":" + maxSizeNode.Attributes["Size"].Value + ":" + attribute.Value
                    };

                    btn.Click += new EventHandler(btnTemplateAlias_Click);

                    this.panelAttributes.Controls.Add(txtBox);
                    this.panelAttributes.Controls.Add(btn);
                }
                else
                {
                    TextBox txtBox = new TextBox()
                    {
                        Name = "txt" + attribute.Name,
                        Text = attribute.Value,
                        Width = 300,
                        Location = new Point(110, index),
                        ReadOnly = true,
                        Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top
                    };

                    this.panelAttributes.Controls.Add(txtBox);
                }
                
                index += 30;
            }
        }

        void btnTemplateAlias_Click(object sender, EventArgs e)
        {
            string[] terms = ((Button)sender).Tag.ToString().Split(':');

            TreeNode[] nodes = this.tvRibbon.Nodes.Find(terms[0], true);

            if (nodes.Count() > 0)
            {
                nodes = nodes[0].Nodes.Find(terms[1], true);

                if (nodes.Count() > 0)
                {
                    nodes = nodes[0].Nodes.Find(terms[2], true);

                    if (nodes.Count() > 0)
                    {
                        this.tvRibbon.SelectedNode = nodes[0];
                        this.tvRibbon.Focus();
                        this.tvRibbon_NodeMouseClick(this.tvRibbon, new TreeNodeMouseClickEventArgs(nodes[0], MouseButtons.Left, 1, 0, 0));
                    }
                }
            }
        }

        void btn_Click(object sender, EventArgs e)
        {
            string term = ((Button)sender).Tag.ToString();

            TreeNode[] nodes = this.tvRibbon.Nodes.Find(term.Replace(currentSelectedEntity, "{!EntityLogicalName}"), true);

            if (nodes.Count() > 0)
            {
                this.tvRibbon.SelectedNode = nodes[nodes.Count() - 1];
                this.tvRibbon.Focus();
                this.tvRibbon_NodeMouseClick(this.tvRibbon, new TreeNodeMouseClickEventArgs(nodes[nodes.Count() - 1], MouseButtons.Left, 1, 0, 0));
            }
            else
            {
                nodes = this.tvRibbon.Nodes.Find(term, true);

                if (nodes.Count() > 0)
                {
                    this.tvRibbon.SelectedNode = nodes[nodes.Count() - 1];
                    this.tvRibbon.Focus();
                    this.tvRibbon_NodeMouseClick(this.tvRibbon, new TreeNodeMouseClickEventArgs(nodes[nodes.Count() - 1], MouseButtons.Left, 1, 0, 0));
                }
            }
        }

        private void tsbRate_Click(object sender, EventArgs e)
        {
            Process.Start("http://ribbonbrowser.codeplex.com/releases");
        }

        private void tsbDiscuss_Click(object sender, EventArgs e)
        {
            Process.Start("http://ribbonbrowser.codeplex.com/discussions");
        }

        private void tsbReportBug_Click(object sender, EventArgs e)
        {
            Process.Start("http://ribbonbrowser.codeplex.com/WorkItem/Create");
        }

        private void tsbDonate_Click(object sender, EventArgs e)
        {
            string url = "";

            string business = "tanguy92@hotmail.com";  // your paypal email
            string description = "Donation%20for%20MSCRM%20Tools";            // '%20' represents a space. remember HTML!
            string country = "FR";                  // AU, US, etc.
            string currency = "EUR";                 // AUD, USD, etc.

            url += "https://www.paypal.com/cgi-bin/webscr" +
                "?cmd=" + "_donations" +
                "&business=" + business +
                "&lc=" + country +
                "&item_name=" + description +
                "&currency_code=" + currency +
                "&bn=" + "PP%2dDonationsBF";

            System.Diagnostics.Process.Start(url);

        }

        private void tsbDefineProxySettings_Click(object sender, EventArgs e)
        {
            this.cManager.EditProxySettings(this);
        }
    }
}
